/*
 * CsvThermistorTableReader.cpp
 *
 * Copyright 2012 Pieter Agten
 *
 * This file is part of tltgen.
 *
 * tltgen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * tltgen is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with tltgen.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file   CsvThermistorTableReader.cpp
 * @author Pieter Agten (pieter.agten@gmail.com)
 * @date   12 feb 2013
 */

#include <algorithm>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

#include "ThermistorTable.hpp"
#include "ThermistorTableRow.hpp"
#include "TemperatureScale.hpp"

#include "CsvThermistorTableReader.hpp"

using boost::shared_ptr;
using boost::make_shared;

namespace tltgen {

  CsvThermistorTableReader::CsvThermistorTableReader(unsigned int temperatureColumn, unsigned int otherColumn, 
						     ThermistorTableRow::Column otherColumnType, const shared_ptr<TemperatureScale>& temperatureScale, 
						     const shared_ptr<Circuit>& circuit, const shared_ptr<AnalogToDigitalConverter>& adc,
						     unsigned int nbLinesToSkip, char separator) :
    temperatureColumn(temperatureColumn), otherColumn(otherColumn), otherColumnType(otherColumnType), 
    temperatureScale(temperatureScale), circuit(circuit), analogToDigitalConverter(adc),
    nbLinesToSkip(nbLinesToSkip), separator(separator)
  {
    if (! isValidColumns(temperatureColumn, otherColumn)) {
      throw std::invalid_argument("The specified columns are invalid.");
    }
    if (! isValidOtherColumnType(otherColumnType)) {
      throw std::invalid_argument("The specified other column type is invalid.");
    }
    if (! isValidTemperatureScale(temperatureScale)) {
      throw std::invalid_argument("The specified temperature scale is invalid.");
    }
    if (! isValidCircuit(circuit)) {
      throw std::invalid_argument("The specified circuit is invalid.");
    }
    if (! isValidAnalogToDigitalConverter(adc)) {
      throw std::invalid_argument("The specified analog to digital converter is invalid.");
    }
    if (! isValidNbLinesToSkip(nbLinesToSkip)) {
      throw std::invalid_argument("The specified number of lines to skip is invalid.");
    }
    if (! isValidSeparator(separator)) {
      throw std::invalid_argument("The specified separator is invalid.");
    }
  }

  bool CsvThermistorTableReader::isValidColumns(unsigned int temperatureColumn, unsigned int otherColumn)
  {
    return otherColumn != temperatureColumn;
  }
  bool CsvThermistorTableReader::isValidOtherColumnType(ThermistorTableRow::Column otherColumnType)
  {
    return otherColumnType != ThermistorTableRow::TEMPERATURE &&
      otherColumnType != ThermistorTableRow::NB_COLUMNS;
  }
  bool CsvThermistorTableReader::isValidTemperatureScale(const shared_ptr<TemperatureScale>& temperatureScale)
  {
    return temperatureScale.get() != NULL;
  }
  bool CsvThermistorTableReader::isValidCircuit(const shared_ptr<Circuit>& circuit)
  {
    return circuit.get() != NULL;
  }
  bool CsvThermistorTableReader::isValidAnalogToDigitalConverter(const shared_ptr<AnalogToDigitalConverter>& adc)
  {
    return adc.get() != NULL;
  }
  bool CsvThermistorTableReader::isValidNbLinesToSkip(unsigned int value)
  {
    return true;
  }
  bool CsvThermistorTableReader::isValidSeparator(char value)
  {
    return true;
  }


  unsigned int CsvThermistorTableReader::getTemperatureColumn() const
  {
    return this->temperatureColumn;
  }
  unsigned int CsvThermistorTableReader::getOtherColumn() const
  {
    return this->otherColumn;
  }
  ThermistorTableRow::Column CsvThermistorTableReader::getOtherColumnType() const
  {
    return this->otherColumnType;
  }
  shared_ptr<TemperatureScale> CsvThermistorTableReader::getTemperatureScale() const
  {
    return this->temperatureScale;
  }
  shared_ptr<Circuit> CsvThermistorTableReader::getCircuit() const
  {
    return this->circuit;
  }
  shared_ptr<AnalogToDigitalConverter> CsvThermistorTableReader::getAnalogToDigitalConverter() const
  {
    return this->analogToDigitalConverter;
  }
  unsigned int CsvThermistorTableReader::getNbLinesToSkip() const
  {
    return this->nbLinesToSkip;
  }
  char CsvThermistorTableReader::getSeparator() const
  {
    return this->separator;
  }


  shared_ptr<ThermistorTable> CsvThermistorTableReader::read(std::istream& in) const
  {
    shared_ptr<ThermistorTable> thermistorTable = make_shared<ThermistorTable>();
    thermistorTable->setCircuit(getCircuit());
    thermistorTable->setAnalogToDigitalConverter(getAnalogToDigitalConverter());
    
    thermistorTable->beginBatch();
    unsigned int row = 0;
    while (!in.eof()) {
      if (row >= getNbLinesToSkip()) {
	std::string line;
	std::getline(in, line);
	line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); // Remove carriage return

	if (! line.empty()) {
	  std::stringstream lineStream(line);
	  unsigned int col = 0;
	  std::string temperatureString;
	  std::string otherString;
	  while (!lineStream.eof() && col <= std::max(getTemperatureColumn(), getOtherColumn())) {
	    lineStream >> std::ws; // skip leading whitespace
	    std::string cell;
	    std::getline(lineStream, cell, getSeparator());
	    if (col == getTemperatureColumn()) {
	      temperatureString = cell;
	    } else if (col == getOtherColumn()) {
	      otherString = cell;
	    }
	    col += 1;
	  }
	  
	  bool success = true;
	  Temperature kelvin_t;
	  try {
	    Temperature t = boost::lexical_cast<Temperature>(temperatureString);
	    kelvin_t = getTemperatureScale()->toKelvin(t);
	    if (! thermistorTable->isValidTemperature(kelvin_t)) throw boost::bad_lexical_cast();
	  } catch (boost::bad_lexical_cast& e) {
	    std::cerr << "[CSV reader] Ignoring row " << row << " because the temperature is invalid: '" << temperatureString << "'." << std::endl;
	    success = false;
	  }
	  
	  double otherValue;
	  try {
	    otherValue = boost::lexical_cast<double>(otherString.c_str());
	    if (! thermistorTable->isValidValue(getOtherColumnType(), otherValue)) throw boost::bad_lexical_cast();
	  } catch (boost::bad_lexical_cast& e) {
	    std::cerr << "[CSV reader] Ignoring row " << row << " because the other column is invalid: '" << otherString << "'." << std::endl;
	    success = false;
	  }
	  
	  if (success) {
	    ThermistorTableRow* newRow = thermistorTable->createRow();
	    newRow->setTemperature(kelvin_t);
	    newRow->setValue(getOtherColumnType(), otherValue);
	  }
	}
      }
      row += 1;
    }
    thermistorTable->endBatch();
    return thermistorTable;
  }

}

